<?php

namespace luo;

Class Cookie
{

    private static $instance = null;

    /**
     * Cookie 名称
     *
     * @var string
     */
    private $name = '';

    /**
     * Cookie 值。
     * 这个值储存于用户的电脑里，请勿储存敏感信息。
     * 比如 name 是 'cookiename'， 可通过 $_COOKIE['cookiename'] 获取它的值。
     *
     * @var string
     */
    private $value = '';

    /**
     * Cookie 的过期时间。
     * 这是个 Unix 时间戳，即 Unix 纪元以来（格林威治时间 1970 年 1 月 1 日 00:00:00）的秒数。
     * 也就是说，基本可以用 time() 函数的结果加上希望过期的秒数。
     * 或者也可以用 mktime()。
     * time()+60*60*24*30 就是设置 Cookie 30 天后过期。
     * 如果设置成零，或者忽略参数， Cookie 会在会话结束时过期（也就是关掉浏览器时）。
     * 你可能注意到了，expire 使用 Unix 时间戳而非 Wdy, DD-Mon-YYYY HH:MM:SS GMT 这样的日期格式，是因为 PHP 内部作了转换。
     *
     * @var int 默认3600秒
     */
    private $expire = 3600;

    /**
     * Cookie 有效的服务器路径。
     * 设置成 '/' 时，Cookie 对整个域名 domain 有效。
     * 如果设置成 '/foo/'， Cookie 仅仅对 domain 中 /foo/ 目录及其子目录有效（比如 /foo/bar/）。
     * 默认值是设置 Cookie 时的当前目录。
     *
     * @var string
     */
    private $path = '/';

    /**
     * Cookie 的有效域名/子域名。
     * 设置成子域名（例如 'www.example.com'），会使 Cookie 对这个子域名和它的三级域名有效（例如 w2.www.example.com）。
     * 要让 Cookie 对整个域名有效（包括它的全部子域名），只要设置成域名就可以了（这个例子里是 'example.com'）。
     * 旧版浏览器仍然在使用废弃的 » RFC 2109， 需要一个前置的点 . 来匹配所有子域名。
     *
     * @var string
     */
    private $domain = '';

    /**
     * 设置这个 Cookie 是否仅仅通过安全的 HTTPS 连接传给客户端。
     * 设置成 TRUE 时，只有安全连接存在时才会设置 Cookie。
     * 如果是在服务器端处理这个需求，程序员需要仅仅在安全连接上发送此类 Cookie （通过 $_SERVER["HTTPS"] 判断）。
     *
     * @var bool
     */
    private $secure = false;

    /**
     * 设置成 TRUE，Cookie 仅可通过 HTTP 协议访问。
     * 这意思就是 Cookie 无法通过类似 JavaScript 这样的脚本语言访问。
     * 要有效减少 XSS 攻击时的身份窃取行为，可建议用此设置（虽然不是所有浏览器都支持），不过这个说法经常有争议。
     * PHP 5.2.0 中添加。 TRUE 或 FALSE
     *
     * @var bool
     */
    private $httponly = false;

    /**
     * 加密KEY
     * @var string
     */
    private static $cryptKey = 'insert_crypt_key:eluophp-v1.0.0';

    /**
     * Cookie constructor.
     * @param string $name
     * @param string $value
     * @param int $expire
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     */
    function __construct($name = '', $value = '', $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = false)
    {
        $this->handleArgs(
            $name,
            $value,
            $expire,
            $path,
            $domain,
            $secure,
            $httponly
        );
    }

    /**
     * 静态方法获取对象
     * @param string $name
     * @param string $value
     * @param int $expire
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     * @return Cookie|null
     */
    public static function instance($name = '', $value = '', $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = false){

        if(!(self::$instance instanceof self)){
            self::$instance = new self($name, $value, $expire, $path, $domain, $secure, $httponly);
        }
        return self::$instance;

    }

    /**
     * setcookie() 定义了 Cookie，会和剩下的 HTTP 头一起发送给客户端。
     * 和其他 HTTP 头一样，必须在脚本产生任意输出之前发送 Cookie（由于协议的限制）。
     * 请在产生任何输出之前（包括 <html> 和 <head> 或者空格）调用本函数。
     * 一旦设置 Cookie 后，下次打开页面时可以使用 $_COOKIE 读取。 Cookie 值同样也存在于 $_REQUEST。
     *
     * @return bool
     */
    private function setcookie(): bool
    {

        // 有效期
        $expire = $this->expire + time();
        return setcookie (
            $this->name,
            $this->value,
            $expire,
            $this->path,
            $this->domain,
            $this->secure,
            $this->httponly
        );

    }

    /**
     * 处理参数
     * @param $name
     * @param $value
     * @param $expire
     * @param $path
     * @param $domain
     * @param $secure
     * @param $httponly
     */
    private function handleArgs($name, $value, $expire, $path, $domain, $secure, $httponly){

        $this->name     = $name;
        $this->value    = $this->crypto($value ?? $this->value);
        $this->expire   = $expire ? $expire : $this->expire;
        $this->path     = $path     ?? $this->path;
        $this->domain   = $domain   ?? $this->domain;
        $this->secure   = $secure   ?? $this->secure;
        $this->httponly = $httponly ?? $this->httponly;

    }

    /**
     * 设置cookie内容
     * @param string $name
     * @param string $value
     * @param int $expire
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     * @return bool
     */
    public function set($name, $value = '', $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = false){

        $this->handleArgs(
            $name,
            $value,
            $expire,
            $path,
            $domain,
            $secure,
            $httponly
        );

        return $this->setcookie();

    }

    /**
     * 获取cookie内容
     * @param $name
     * @return bool
     */
    public function get(?string $name)
    {

        $cookie = $_COOKIE ?? false;
        if($name){
            $cookie = $_COOKIE[$name] ?? false;
            $cookie = $this->crypto($cookie, false);
        }
        return $cookie;

    }

    /**
     * 删除cookie内容
     * @param  $name
     * @return mixed
     */
    public function delete(?string $name){

        $this->expire = - $this->expire;
        $this->name = $name;
        $this->setcookie();
        if(isset($_COOKIE[$name])){
            unset($_COOKIE[$name]);
        }
        return null;

    }

    /**
     * 加密解密
     * @param $data
     * @param bool $encrypt
     * @return mixed|string
     */
    private function crypto($data, $encrypt = true){

        // 加密KEY
        $key = md5(self::$cryptKey);
        // 非 NULL 的初始化向量
        $iv  = base64_encode(substr($key,0,strlen($key) / 2));
        if($encrypt){
            // 实例化要加密的数据
            $info    = serialize($data);
            // openssl_encrypt 加密
            $data    = openssl_encrypt($info, 'AES-256-CBC',$key,0,base64_decode($iv));
            $data = base64_encode(json_encode($data));
        }else{
            // 还原
            $data = json_decode(base64_decode($data), true);
            // 解密
            $decrypt = openssl_decrypt($data, 'AES-256-CBC', $key, 0, base64_decode($iv));
            $data    = unserialize($decrypt);
        }
        return $data;

    }

}